//
// Created by mabinjie on 2024/10/29.
//

#ifndef WAITABLE_HPP
#define WAITABLE_HPP
#define TIME_TO_WAIT std::chrono::seconds(5)
#define ANY_BODY_TYPE uint16_t{0}
#include <cstdint>
#include <mutex>

#include <semaphore>

#include "common/logger.hpp"
#include "common/concepts.hpp"

namespace network {

template <common::CommonResponseType response_t>
class Waitable {
public:
    std::pair<bool, protocol::Packet> wait_for_next(const uint16_t next, std::chrono::seconds wait_time = TIME_TO_WAIT) {
        {
            std::unique_lock lock(mutex_);
            logger->info("Waiting for a message with id = {:X}", next);
            ++id_waiting_counts_[next];
            id_semaphore_.try_emplace(next, 0);
        }
        if (id_semaphore_.at(next).try_acquire_until(std::chrono::system_clock::now() + wait_time)) {
            logger->info("Waiting for a message with id = {:X} successful", next);
            return {true, packet_};
        }
        logger->info("Waiting for a message with id = {:X} faild", next);
        return {false, protocol::Packet{}};
    }

    std::pair<bool, protocol::Packet> wait_for(const uint16_t message_sn, std::chrono::seconds wait_time = TIME_TO_WAIT) {
        {
            std::unique_lock lock(mutex_);
            logger->info("Waiting for message who response to message with sn = {:X}", message_sn);
            ++sn_waiting_counts_[message_sn];
            sn_semaphore_.try_emplace(message_sn, 0);
        }
        if (sn_semaphore_.at(message_sn).try_acquire_until(std::chrono::system_clock::now() + wait_time)) {
            logger->info("Waiting for message who response to message with sn = {:X} successful", message_sn);
            return {true, packet_};
        }
        logger->info("Waiting for message who response to message with sn = {:X} faild", message_sn);
        return {false, protocol::Packet{}};
    }

    void get_it(protocol::Packet p, bool who_is_id) {
        std::lock_guard lock(mutex_);
        uint16_t who = who_is_id ? p->header_.message_id_ : reinterpret_cast<response_t*>(p->body_.get())->message_sn_to_response_;
        if (who_is_id) {
            logger->info("Get a message with id = {:X}", who);
            if (auto found = id_waiting_counts_.find(who); found != id_waiting_counts_.end() && found->second > 0) {
                if (id_semaphore_.contains(found->first)) {
                    id_semaphore_.at(found->first).release(found->second);
                    packet_ = p;
                    found->second = 0;
                }
            }
        } else {
            logger->info("Get a message with sn = {}", (int)who);
            if (auto found = sn_waiting_counts_.find(who); found != sn_waiting_counts_.end() && found->second > 0) {
                if (sn_semaphore_.contains(found->first)) {
                    sn_semaphore_.at(found->first).release(found->second);
                    packet_ = p;
                    found->second = 0;
                }
            }
        }
    }

    virtual ~Waitable() = default;

protected:
    std::mutex mutex_; // 互斥锁
    std::unordered_map<uint16_t, std::counting_semaphore<>> id_semaphore_, sn_semaphore_;
    std::unordered_map<uint16_t, int> id_waiting_counts_, sn_waiting_counts_;
    protocol::Packet packet_;
    logger_t logger = common::Logger::logger("Waitable");
};

}

#endif //WAITABLE_HPP
